feat: add @metamask/platform-api-docs package#8012
Conversation
63f3188 to
7c63a0d
Compare
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub. |
|
Caution MetaMask internal reviewing guidelines:
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
b55b682 to
980f677
Compare
|
@metamaskbot publish-preview |
|
Preview builds have been published. See these instructions for more information about preview builds. Expand for full list of packages and versions. |
980f677 to
f49a7dd
Compare
|
@metamaskbot publish-preview |
1 similar comment
|
@metamaskbot publish-preview |
f49a7dd to
44fb063
Compare
|
@metamaskbot publish-preview |
@metamask/messenger-docs
@metamask/messenger-docs@metamask/messenger-docs package
Docusaurus site for browsing controller messenger actions/events, with offline search powered by docusaurus-search-local.
## Explanation The messenger docs generation currently lives in `scripts/generate-messenger-docs/` and the Docusaurus site template in `docs-site/`. This makes it unusable by external clients (metamask-extension, metamask-mobile) without access to this monorepo. This PR extracts both into a new `@metamask/messenger-docs` package at `packages/messenger-docs/` with a CLI, so any project with `@metamask` controller dependencies can generate and serve messenger API docs. ### Usage ```bash # Default: scans cwd for node_modules/@MetaMask controller/service packages npx @metamask/messenger-docs # Scan a specific project npx @metamask/messenger-docs /path/to/project # Generate + build static site npx @metamask/messenger-docs --build # Generate + serve (build + http server) npx @metamask/messenger-docs --serve # Generate + dev server (hot reload) npx @metamask/messenger-docs --dev # Scan source .ts files instead of .d.cts (for monorepo development) npx @metamask/messenger-docs --source # Custom output directory (default: .messenger-docs) npx @metamask/messenger-docs --output ./my-docs ``` ## References - Builds on top of `feat/messenger-docs-site` ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them
cb82c24 to
17adacd
Compare
Re-applies an earlier change that was lost during the messenger-docs package split. `findTsFiles` and `findDtsFiles` are now thin wrappers around `glob`, which replaces the hand-rolled recursive readdir walks. `glob` doesn't guarantee result order, so the matches are explicitly sorted before being returned. Downstream consumers (extraction, dedup, output ordering) depend on a stable file processing order, so this keeps generated output deterministic across filesystems.
The package was originally named for where it lived in the codebase
(alongside `messenger-action-types`), but its actual product is the
Platform API documentation site: the catalog of actions and events
available through the message bus. The site title, the workflow
("Deploy Platform API Docs"), and the README copy all already use
"Platform API"; the package and CLI name were the last lingering bits
of the old framing.
Renames in this commit:
- Directory: `packages/messenger-docs/` → `packages/platform-api-docs/`.
- npm name: `@metamask/messenger-docs` → `@metamask/platform-api-docs`.
- Binary: `messenger-docs` → `platform-api-docs` (now explicitly named
in `bin` rather than implicitly derived from the package name).
- Root scripts: `docs:messenger:{build,dev,serve}` → `docs:platform-api:*`.
- Output directory: `.messenger-docs/` → `.platform-api-docs/`.
- Generated `sidebars.ts` header comment and the synthetic Docusaurus
site's `package.json#name` updated to match.
- CODEOWNERS, teams.json, tsconfig.{json,build.json}, eslint.config.mjs,
yarn.config.cjs, .gitignore, and the workflow file all updated to
reference the new path/name.
The package is still at 0.0.0 with no consumers, so there's no
migration path needed.
The `node_modules/` branch in `generateItemMarkdown` was producing a nonsensical link when the path wasn't `@metamask/`-scoped: the fallback used the entire source path as the package name, yielding URLs like `https://www.npmjs.com/package/node_modules/some-vendor/dist/index.d.ts`. In practice the scanner only walks `node_modules/@metamask/*/dist`, so the fallback never fired against real input. The only thing exercising it was a unit test I added in the coverage-bump commit — so the test was effectively codifying broken output to inflate coverage. Restructure to: render an npm link only when the path is actually `@metamask`-scoped, otherwise fall through to the normal source-link branches (GitHub blob URL if a repo base URL is available, plain path otherwise). The two unit tests now assert the correct behavior: - a plain `**Source**: \`<path>:<line>\`` when no repo URL is given - a GitHub blob URL when one is Reported by Cursor Bugbot on PR #8012.
`actions/deploy-pages` deploys to the root of the gh-pages site, which means a single repo can only publish one doc site through it. If we ever want to add package API docs (or any other doc surface) alongside, the platform-api site needs to live in its own subdirectory. Switch the deploy mechanism to `peaceiris/actions-gh-pages` and: - Publish to `gh-pages/platform-api/` via `destination_dir: platform-api`. - Set `keep_files: true` so sibling subdirectories (e.g. a future `/package-api/`) aren't clobbered on each platform-api deploy. - Update `DOCS_BASE_URL` to `/<repo>/platform-api/` so the built Docusaurus site has the right `<base href>`. - Replace the `actions/upload-pages-artifact` step with a regular `actions/upload-artifact` + `actions/download-artifact` pair, since we no longer use the Pages-specific artifact pipeline. - Swap the `pages: write` / `id-token: write` permissions for the deploy job to `contents: write` (peaceiris pushes the build commit directly to `gh-pages`). - Drop the `environment: github-pages` block; with peaceiris, the deploy isn't gated on a Pages deployment environment. - Bump pinned action versions to match the freshest in this repo: `MetaMask/action-checkout-and-setup@v3`, `actions/upload-artifact@v6`, `actions/download-artifact@v7`, `peaceiris/actions-gh-pages@v4.1.0`. Note: when this lands, the repo's GitHub Pages settings need to be flipped from "GitHub Actions" back to "Deploy from a branch" (gh-pages) since we're no longer using the Pages Actions pipeline.
`resolveHandler` was matching only the single-quoted form `ClassName['method']`. TypeScript accepts three syntactically distinct forms for indexed-access types — `Class['m']`, `Class["m"]`, and `` Class[`m`] `` — and ts-morph's `getText()` returns whatever the source used verbatim. When a controller author chose double quotes or template literals, we'd fail to resolve the handler and fall back to rendering the raw text instead of the method signature plus its JSDoc. Updated the regex to a backreferenced quote character class (`/^(\w+)\[(['"`])(\w+)\2\]$/u`) and shifted the method-name capture group from `match[2]` to `match[3]` to compensate. Added two tests covering the double-quoted and template-literal forms. Bump the action versions in the deploy workflow to the absolute latest pair (`upload-artifact@v7` / `download-artifact@v8`), since these are paired by major version and v8 of `download-artifact` flips hash-mismatch handling from warning to error (a strictly-better default for an artifact-driven deploy). Reported by Cursor Bugbot on PR #8012.
The site was rendering action handlers as raw TypeScript types — fine
when the types are clean, but ugly when ts-morph emits a fully-qualified
`import("/abs/path/...").TypeName`. The information engineers actually
want — what each handler argument means and what it returns — was
being thrown away (the `@param` / `@returns` tags were stripped by the
JSDoc parser before reaching the renderer).
Keep those tags as structured data and render them alongside the
handler signature:
- Add `params: { name, description }[]` and `returns: string` fields to
`ExtractedMessengerCapabilityType` (and `MethodInfo` for inheritance).
- Replace `extractJsDocText` with `extractJsDoc`, which decomposes the
comment into `{ description, params, returns }` — the description
body still flows through MDX-escaping and `{@link}` resolution, but
`@param` and `@returns` tags are pulled out into separate fields.
- Strip the conventional leading `- ` separator from `@param` comments
(JSDoc style is `@param name - description`; the hyphen is purely
decorative).
- When an action's handler resolves to a class method, inherit the
method's `@param`/`@returns` if the type alias doesn't have its own.
Type-alias-level docs win when both are present.
- In `markdown.ts`, render `**Parameters**:` as a name/description
table and a `**Returns**:` line, both with backtick-reference
linkification. The existing signature block is renamed to
`**Handler signature**:` / `**Payload signature**:` so the new sections
read naturally.
Events don't get a parameters table — they carry positional payload
tuples, not named arguments.
Coverage held at 100% lines / 94% branches with new fixtures for the
structured-fields path, multi-line params, and class-method inheritance
(including the type-alias-overrides-method ordering rule).
Most messenger capability types in this repo live in auto-generated `*-method-action-types.ts` files alongside their controller, with the *Messenger declaration importing an umbrella union from that sibling. The previous extractor anchored on the file containing the messenger and refused to follow imports — so for `NetworkController.ts` only `NetworkControllerGetStateAction` was being extracted, despite 24 other actions sitting in the sibling file. Switch to a single ts-morph Project shared across every scanned file, with a real filesystem (not the in-memory FS we were using before) so the type checker can resolve imports normally. The walker now follows imported references via `symbol.getAliasedSymbol()` to find the original declaration in its home file. Same shape for `Class['method']` handler resolution — instead of regex-matching the text, we look at the actual `IndexedAccessTypeNode`, resolve the object type's symbol to the class declaration (across files when needed), and look up the method by name. Several pieces of supporting machinery dropped or shrank: - `collectStringConstants` / `extractStringConstants` and the manual single-hop import-chasing are gone. `typeof X` now resolves through the type checker (`.getType().getLiteralValueOrThrow()`). - The per-file `collectClassMethods` cache is gone — methods are looked up on demand via the class declaration the type checker points to. - The regex-based `resolveHandler` is gone. Replaced by `resolveIndexedAccessMethod` which inspects the AST. - The walker now distinguishes between a TypeReference with no type arguments (a plain re-export to leave to the home package) and one with type arguments (a capability-type-constructor invocation worth recording). - The walker's seen-set is keyed by declaration node, not name, so the same name reached through two messengers in the same file records the declaration once. `extractFromFile` is preserved as a convenience wrapper for tests: it creates a fresh Project, adds the file plus its parent directory's siblings so relative imports resolve, and calls `extractFromSourceFile`. The new entry points are `createExtractionProject` + `extractFromSourceFile`, which `generate.ts` uses for the batch pipeline so one Project amortizes the type-checker setup across the whole run. Live build numbers: 200 messenger items / 75 namespaces → 1058 items / 88 namespaces. NetworkController went from 1 action to 25; ApprovalController from 1 to 17, with the rich `@param` JSDoc from the auto-generated files now flowing into the rendered Parameters tables. Coverage threshold adjusted to the new baseline (97/90/97/96 lines / branches / statements / functions). The drop is mostly defensive guards in the new symbol-resolution paths that don't fire in our fixtures (e.g. symbols whose aliased target has no declarations).
The previous return type `TypeElementTypes | null` left the caller with a union that included shapes like `GetAccessorDeclaration` that don't have `getTypeNode()`, which the package's strict tsc build rejected. Capability bodies only ever contain property signatures — narrow the return type explicitly so the callers can read `getTypeNode` without a cast.
| - name: Generate and build Platform API docs | ||
| run: yarn docs:platform-api:build | ||
| env: | ||
| DOCS_URL: https://${{ github.repository_owner }}.github.io |
There was a problem hiding this comment.
I know I had a question about this earlier, but instead of setting environment variables, does it make sense to pass these values as options to the CLI inside of the yarn docs:platform-api:* package scripts?. In the Docusaurus config file we still need to read environment variables, so we need to set them at some point — but from the perspective of the CLI and thus what users see, it would be good to have only one way to specify options instead of two ways.
| * @param projectPath - Project root, used for computing relative source paths. | ||
| * @returns The extracted capability, or null if the shape doesn't match. | ||
| */ | ||
| function extractFromInlineMessengerCapabilityType( |
There was a problem hiding this comment.
Hmm. This and resolveInlineTypeString seems complicated. I was able to produce a version on my side based on a previous commit that was less complicated. I need to understand the difference between what I have and what you have.
There was a problem hiding this comment.
Fair — happy to compare. Could you push or paste your version so we can put the two side by side?
The reason the current pair looks heavier than what you'd get off an earlier commit is mostly the work landed in a85fa0f (cross-file type resolution). A quick map of what each function is carrying:
extractFromInlineMessengerCapabilityType:- Accepts both
TypeAliasDeclaration(where the body is theTypeLiteral) andInterfaceDeclaration(where the declaration is the member list) — that's the firstif/elseblock. - Resolves
typeStringviaresolveInlineTypeStringto handle the`${typeof controllerName}:action`template-literal pattern that's common in the auto-generated*-method-action-types.tsfiles. - For actions, if the
handlerresolves toClass['method'](possibly in another file), it inherits the method's signature + any JSDoc fields the alias doesn't already provide. That's the chunk underif (kind === 'action').
- Accepts both
resolveInlineTypeStringis essentially just "walk to thetypemember, then dispatch on literal vs template-literal type node." It looks long because of the// istanbul ignoreannotations I added in d4160fd — without those it's ~15 LOC.
If your version drops the Class['method'] inheritance and/or the template-literal path, that's the delta — those features are what got us from 200 → 1058 extracted items. If it keeps them but expresses them more compactly, I'd love to take that improvement.
- Dual-license under MIT + Apache 2.0, matching the new package template: `LICENSE` is now the umbrella pointer, with the MIT text moved to `LICENSE.MIT` and `LICENSE.APACHE2` added. - Add the PR link to the unreleased changelog entry. - Rename `ParamDoc` → `DocumentedParameter` (the previous name was cryptic-shorthand). - Replace inline `// e.g. "..."` field comments in `types.ts` with proper JSDoc on every public field — both for consistency with the rest of the file and so type aliases render usefully in editor hover.
Four interlinked cleanups in the platform-api-docs CLI:
- Locate the Docusaurus binary via `npm-which` instead of hardcoding
`require.resolve('@docusaurus/core/bin/docusaurus.mjs')`. A future
Docusaurus upgrade can move its binary anywhere within the package;
`npm-which` follows whatever ends up in `node_modules/.bin`.
- Drop the `NODE_PATH` override that surrounded the Docusaurus
invocation. With the binary located directly via `npm-which`, there
was no remaining reason to inject `NODE_PATH` — Node's normal module
resolution from the binary's own location works.
- Drop the React-alias block from `docusaurus.config.ts`. It only
existed to paper over the `NODE_PATH` override, looking up
`react`/`react-dom`/`@mdx-js/react` at the injected path. Without
the override there's nothing for it to do; the package's own
`node_modules` carries the React versions Docusaurus needs.
- Replace the hand-rolled `copyDir` helper with `fs.cp(...,
{ recursive: true, filter })`. The lint rule complains because the
API only got the "stable" marker in Node 22.3, but it's been
functional throughout the package's supported range
(`^18.18 || >=20`); a targeted disable with the rationale stays.
End-to-end build still produces 1058 messenger items across 88
namespaces.
|
@metamaskbot publish-preview |
Replace the workflow's `DOCS_URL` and `DOCS_BASE_URL` environment variables with `--site-url` and `--site-base-url` flags on the docs CLI. The CLI now owns the flag-to-env-var translation at the subprocess boundary, so callers (workflow files, package scripts) don't need to know how the values are plumbed through to Docusaurus.
The dedup-scoring helper strips a trailing `Controller` or `Service` from a namespace to derive the home-package prefix it then searches for in the source file path. The previous regex `/Controller|Service/u` matched the first occurrence anywhere, so a namespace like `ServiceWorkerController` lost its leading `Service` instead of the trailing `Controller`, producing the wrong prefix and picking the wrong duplicate. Anchor the alternation with `$` so only a suffix is stripped.
The build-docs job produces an artifact that gets deployed to GitHub Pages on pushes to main, so the checkout-and-setup action's `is-high-risk-environment` flag should be `true` per its README (production environments and deployable artifacts qualify). This matches the precedent set by the utils repo's API docs workflow.
Closes the remaining coverage gaps in `generate.ts` and `extraction.ts` and raises the jest thresholds to 100% across the board: - Four new tests cover paths that were live behavior but exercised by no fixture: in-namespace action/event sort, the reverse direction of `replaceDuplicateInGroup` (event replaced by action), symlinked `@metamask/*` packages in `node_modules`, bare TypeReference re-export skip, and optional handler parameters marked with `?`. - The remaining defensive AST guards (malformed indexed-access shapes, missing symbols, etc.) get `// istanbul ignore next` annotations explaining why they don't fire on valid messenger fixtures.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1b05df4. Configure here.
- Drop the now-stale `webpackbar@^6.0.1` → `^7.0.0` resolution at package.json:107. It was added in 985c43c as a temporary workaround for the Docusaurus 3.10.0 / webpack 5.106.0 incompatibility tracked in facebook/docusaurus#11923, with explicit guidance to revert once Docusaurus 3.10.1 shipped. We're on 3.10.1 now and `@docusaurus/bundler` requests `webpackbar` `^7.0.0` directly, so the selector matches nothing. - Declare `@docusaurus/theme-common` and `@docusaurus/plugin-content-docs` as direct deps of `@metamask/platform-api-docs` to satisfy yarn's peer-dep checks for `@easyops-cn/docusaurus-search-local` (which peer-depends on theme-common) and theme-common (which peer-depends on plugin-content-docs). Both packages were already pulled in transitively via `preset-classic`; the explicit declarations document what the site actually relies on.
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
Make `site/tsconfig.json` extend the monorepo's shared `tsconfig.base.json`, keeping only the overrides Docusaurus actually needs (composite off, noEmit, JSX, ESNext modules, Bundler resolution). Drop the redundant `target`, `esModuleInterop`, and `strict` fields now that they come from base. Also stop excluding `packages/platform-api-docs/site/**` from ESLint so `site/docusaurus.config.ts` is type-checked as part of the normal lint flow rather than being an island only exercised by `yarn docs:platform-api:build`. typescript-eslint's `projectService: true` picks up the site's tsconfig automatically. Move `@docusaurus/types` from devDependencies to dependencies — `site/docusaurus.config.ts` imports `Config` from it, and `site/` is published as part of this package, so a consumer of the published CLI needs the types resolvable when Docusaurus loads the config at runtime.
Including `packages/platform-api-docs/site/**` in ESLint made typescript-eslint's `projectService` discover the site's tsconfig and try to type-check `docusaurus.config.ts`. Loading the full Docusaurus + React + webpack type graph blows past `lint:eslint`'s `--max-old-space-size=6144` cap and crashes CI with a heap-OOM. The `extends`-from-base wiring on `site/tsconfig.json` and the `@docusaurus/types` dependency move from eec9874 are kept — only the ESLint inclusion is reverted.

Explanation
Adds
@metamask/platform-api-docs, a publishable package that generates and serves a searchable site documenting the Platform API — the catalog of actions and events available to clients through the message bus.The package scans TypeScript source and
.d.ctsdeclaration files, finds every*Messengertype declaration, walks itsActionsandEventstype arguments to discover the capability types they reference, extracts JSDoc/handler/payload information for each, and renders the result as a Docusaurus site with per-namespace pages and local search.Features
*Messengertype aliases and walksMessenger<Namespace, Actions, Events>to find the capability types — eliminating false positives from unrelated types that happen to share an action/event-like shape.type FooAction = { type: '...'; handler: ... }) and capability-type constructors (type FooGetStateAction = ControllerGetStateAction<typeof name, State>), including theirinterfacevariants.FooActions = FooGetAction | FooSetAction, the walker descends through it without documenting the intermediate alias.--scan-dirs,packages/*/src/(.ts), andnode_modules/@metamask/*/dist/(.d.cts).git remote get-url originandgit symbolic-ref refs/remotes/origin/HEADto buildhttps://github.com/<owner>/<repo>/blob/<branch>/<path>#L<line>URLs; falls back tomainfor shallow clones.--project-label Core/--project-label Extensionproduces titles likePlatform API (Core), and the short Git commit is shown in the intro and navbar so engineers can tell how current the docs are.Usage
From the core monorepo:
From client projects (Extension, Mobile), install
@metamask/platform-api-docsas a devDependency and add a script:{ "scripts": { "docs:platform-api:build": "platform-api-docs --build --project-label MyProject" } }Implementation
packages/platform-api-docs/— separate workspace from@metamask/messenger-cli(different deps and release cadence).execa. Flags:--build,--serve,--dev,--scan-dir(additive, repeatable),--output,--project-label. Configuration is CLI-only — nopackage.jsonconfig block.jsDoc.getDescription()+getTags()) replaces the previous hand-rolled comment parser.TypeAliasDeclaration/InterfaceDeclarationnodes directly tagged with'action'/'event'kind, so the main loop doesn't re-walk the source file looking up names.globpackage, with results sorted for deterministic output across filesystems.// istanbul ignorecomments.deploy-platform-api-docs.yml) builds docs on PRs (uploads the build as an artifact) and deploys to GitHub Pages onmain.References
Checklist
Note
Medium Risk
Adds a new publishable docs generator that parses TS via
ts-morphand introduces a new GitHub Actions workflow to build/deploy to Pages; main risk is CI/deploy config and new dependency surface rather than runtime code paths.Overview
Adds a new workspace package,
@metamask/platform-api-docs, that generates Platform API documentation by scanning projects for*Messengertype declarations, extracting action/event metadata (JSDoc, handler/payload signatures), and rendering per-namespace markdown plus Docusaurus site assets.Introduces a
deploy-platform-api-docsGitHub Actions workflow that builds docs on PRs (uploads an artifact) and deploys the built site to GitHub Pages onmainunder/platform-api/.Wires the tool into the repo via new root scripts (
docs:platform-api:*), updates ESLint ignores/rules for the new package, adds CODEOWNERS/README entries, and ignores generated output (.platform-api-docs/) in git.Reviewed by Cursor Bugbot for commit 21f1876. Bugbot is set up for automated code reviews on this repo. Configure here.